#! /usr/local/bin/gawk -f
#
# Copyright (C) 2013, 2014, 2015 Arnold David Robbins
# 
# This file is part of TexiWeb Jr., a literate programming system.
# 
# TexiWeb Jr. is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# 
# TexiWeb Jr. is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
#
# Up-to-date source code for TexiWeb Jr. can be obtained via
# Git from github:
#
#	git clone http://github.com/arnoldrobbins/texiwebjr
#
BEGIN {
	TRUE = 1
	FALSE = 0
	File_chunk_pattern = "^@\\(([^)]+)@\\)[[:space:]]*=[[:space:]]*$"
	Code_chunk_pattern = "^@" "<(.+)" "@>[[:space:]]*=[[:space:]]*$"
	Chunk_name_pattern = "@<[^>\n]+@>"
}
# Error checking:

# Use brackets to avoid triggering the warning on ourselves!
/(^<[@])|(>[@]([[:space:]]*=[[:space:]]*)?$)/	{
	# Ditto, with concatenation
	warning("<" "@ or >" "@ used instead of @" "< or @" ">\n\t%s\n",
			$0)
}

END {
	check_unfinished()
}
BEGIN {
	if (ARGC < 2)
		fatal(_"usage: jrweave file.twjr [...]\n")

	Pass = 1
	n = ARGC
	ARGV[ARGC++] = "Pass=2"
	for (i = 1; i < n; i++) {
		if (ARGV[i] == "-" || ARGV[i] == "/dev/stdin")
			fatal(_"jrweave: standard input not allowed\n")
		ARGV[ARGC++] = ARGV[i]
	}
}
Pass == 2 && FNR == 1 && Debug ~ /pass2/ {
	junk++
}
BEGIN {
	print_do_not_edit(ARGV[1])
}

# print_do_not_edit --- create and print warning

function print_do_not_edit(filename,	i, pl, pr, l, s, t)
{
	t = _"DO NOT EDIT THIS FILE!!!!"
	if (ARGC > 4)	# more than one file
		s = sprintf(_"It was created by jrweave from `%s' (or maybe others).",
				filename)
	else
		s = sprintf(_"It was created by jrweave from `%s'.", filename)
	
	l = length(s)
	pl = (l - length(t)) / 2	# padding on left side
	pr = l - (pl + length(t))	# padding on right side
	if (pl * 2 < l)				# account for odd lengths
		pr++
	for (i = 1; i <= l + 4; i++)
		printf("%%")
	printf "\n"

	# print the titles with their padding
	printf("%% %*s%s%*s %%\n", pl, " ", t, pr, " ")
	printf("%% %s %%\n", s)

	for (i = 1; i <= l + 4; i++)
		printf("%%")
	printf "\n"
}
# check_unfinished --- print a fatal error when an unfinished code or
#			file chunk is detected. Also ifweave / iftangle.

function check_unfinished()
{
	if (Flags["file chunk"])
		fatal(_"unfinished file chunk (started at %s)\n",
			Line_numbers["file chunk"])
	else if (Flags["code chunk"])
		fatal(_"unfinished code chunk (started at %s)\n",
			Line_numbers["code chunk"])

	if ("ifweave" in Line_numbers)
		fatal(_"unfinished @ifweave section (started at %s)\n",
			Line_numbers["ifweave"])

	if ("iftangle" in Line_numbers)
		fatal(_"unfinished @iftangle section (started at %s)\n",
			Line_numbers["iftangle"])
}
# strip_out_name --- get the name from name

function strip_out_name(name,	l)
{
	l = length(name)
	name = substr(name, 3, l - 4)

	return name
}
# Helper functions

# message --- write a particular kind of message out to stderr

function message(msg, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)
{
	printf("%s:%d: %s: " format, FILENAME, FNR, msg,
		a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) > "/dev/stderr"

	if (substr(format, length(format), 1) != "\n")
		printf("\n") > "/dev/stderr"
}

# fatal --- print a fatal error message and exit.
#	 No varargs, so fake it with lots of parameters.

function fatal(format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)
{
	message(_"fatal", format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)
	exit 1
}

# warning --- print a warning message to stderr
#	 No varargs, so fake it with lots of parameters.

function warning(format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)
{
	message(_"warning", format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)
}

# join.awk --- join an array into a string
#
# Arnold Robbins, arnold@skeeve.com, Public Domain
# May 1993

function join(array, start, end, sep,    result, i)
{
	if (sep == "")
		sep = " "
	else if (sep == SUBSEP) # magic value
		sep = ""
	result = array[start]
	for (i = start + 1; i <= end; i++)
		result = result sep array[i]
	return result
}
# iftangle lines should be removed

/^@iftangle[[:space:]]*$/, /^@end iftangle[[:space:]]*$/ {
	if (Pass == 1)
		next

	if ("ifweave" in Line_numbers)
		fatal(_"cannot nest @iftangle inside @ifweave\n")

	# start of construct, save line number
	if (/^@iftangle[[:space:]]*$/)
		Line_numbers["iftangle"] = (FILENAME ":" FNR)

	# end of construct, delete line number
	if (/^@end iftangle[[:space:]]*$/)
		delete Line_numbers["iftangle"]

	# simply skip these lines, this is weaving
	next
}

# For weaving we remove the bracketing control lines and let anything
# in between fall through.

/^@ifweave[[:space:]]*$/, /^@end ifweave[[:space:]]*$/ {
	if (Pass == 1)
		next

	if ("iftangle" in Line_numbers)
		fatal(_"cannot nest @ifweave inside @iftangle\n")

	# start of construct, save line number, skip this line
	if (/^@ifweave[[:space:]]*$/) {
		Line_numbers["ifweave"] = (FILENAME ":" FNR)
		next
	}

	# end of construct, delete line number, skip this line
	if (/^@end ifweave[[:space:]]*$/) {
		delete Line_numbers["ifweave"]
		next
	}

	# otherwise fall through into the rest of the code
}
/^@file_update_recipe[[:space:]]*$/,
		/^@end file_update_recipe[[:space:]]*$/ {
	next
}
/^@file_update[[:space:]]/ {
	next
}
/^@post_create[[:space:]]+/ {
	next
}
BEGIN {
	Example_start = "@example"
	Example_end = "@end example"
}
Pass == 2 && /^@use_smallexample[[:space:]]*$/ {
	Example_start = "@smallexample"
	Example_end = "@end smallexample"
	next
}

Pass == 2 && /^@use_example[[:space:]]*$/ {
	Example_start = "@example"
	Example_end = "@end example"
	next
}
Pass == 2 && /^@titlepage/ {
	print "@c Let texinfo.tex give us full section titles"
	print "@xrefautomaticsectiontitle on"
	print ""
	print "@c Start extra commands added by jrweave\n"
	print "@c For HTML, spell out email addresses, to avoid problems with"
	print "@c address harvesters for spammers."
	print "@ifhtml"
	print "@macro EMAIL{real,spelled}"
	print "``\\spelled\\''"
	print "@end macro"
	print "@end ifhtml"
	print "@ifnothtml"
	print "@macro EMAIL{real,spelled}"
	print "@email{\\real\\}"
	print "@end macro"
	print "@end ifnothtml"
	print ""
	print "@macro FIXME{text}"
	print "@strong{FIXME}: \\text\\"
	print "@end macro"
	print ""
	print "@macro oldnum{value}"
	print "\\value\\"
	print "@end macro"
	print ""
	print "@tex"
	print "\\gdef\\oldnum#1{\\begingroup\\oldstyle #1\\endgroup}%"
	print "@end tex"
	print "\n@c End extra commands added by jrweave"

	print ""
	print	# print the line

	next
}
/^@numberedchunks[[:space:]]*$/ {
	if (Pass == 1)
		Numbered_chunks = TRUE
	next
}
/^@file_chunk_full_defs[[:space:]]*$/ {
	if (Pass == 1)
		Print_file_full_defs = TRUE
	next
}
Pass == 2 && /^@sidebar[[:space:]]+/ {
	sub(/^@sidebar[[:space:]]+/, "", $0)
	Sidebar_title = $0
	Sidebar_body = ""
	Collecting_sidebar = TRUE
	next
}

Pass == 2 && /^@end[[:space:]]+sidebar[[:space:]]*$/ {
	Collecting_sidebar = FALSE
	printf "@cindex sidebar, %s\n", Sidebar_title
	printf "@ifdocbook\n"
		printf "@docbook\n"
		printf "<sidebar><title>%s</title>\n", Sidebar_title
		printf "@end docbook\n"
			print Sidebar_body
		print ""
		printf "@docbook\n"
		printf "</sidebar>\n"
		printf "@end docbook\n"
	printf "@end ifdocbook\n\n"

	printf "@ifnotdocbook\n"
		printf "@cartouche\n"
		printf "@center @b{%s}\n\n", Sidebar_title
			print "@noindent"
			sub(/^\n*/, "", Sidebar_body)	# remove initial newlines
			print Sidebar_body
		printf "@end cartouche\n"
	printf "@end ifnotdocbook\n"
	Sidebar_body = ""
	next
}

Pass == 2 && Collecting_sidebar {
	Sidebar_body = Sidebar_body "\n" $0
	next
}
Pass == 2 && /^@dquotexrefs[[:space:]]*$/ {
	print "@tex"
#	print "%\\gdef\\xrefprintnodename#1{{\\it #1}}"
	print "\\gdef\\xrefprintnodename#1{``#1''}"
	print "@end tex"

	next
}
Pass == 2 && /^@pdflinkcolor[[:space:]]*.*$/ {
	if (NF != 1 && NF != 4)
		fatal(_"@pdflinkcolor: wrong number of arguments\n")

	if (NF == 1)
		Link_color = Dark_red
	else {
		$1 = ""
		$0 = $0
		Link_color = $0
	}

	print "@tex"
	print "\\gdef\\linkcolor{" Link_color "}"
	print "@end tex"

	next
}
Pass == 2 && /^@urllinkcolor[[:space:]]*.*$/ {
	if (NF != 1 && NF != 4)
		fatal(_"@urllinkcolor: wrong number of arguments\n")

	if (NF == 1)
		URL_color = Dark_red
	else {
		$1 = ""
		$0 = $0
		URL_color = $0
	}

	print "@tex"
	print "\\gdef\\urlcolor{" URL_color "}"
	print "@end tex"

	next
}
Pass == 2 && /^@hideurls[[:space:]]*$/ {
	print "@tex"
	print "\\global\\urefurlonlylinktrue"	# NOTE: *not* \gdef
	print "@end tex"

	next
}
Pass == 2 && /^@allowindexbraces[[:space:]]*$/ {
	print "@tex"
	print "\\global\\usebracesinindexestrue"	# NOTE: *not* \gdef
	print "@end tex"

	next
}
BEGIN {
	Dark_red = "0.5 0.09 0.12"
}
Pass == 2 && /^@c %\*\*end of header/ {
	print	# print the line

	print "\n@c Extra indices added by jrweave"
	print "@defindex cd   @c chunk definition"
	print "@defindex cr   @c chunk reference"

	next
}
$0 ~ File_chunk_pattern {
	Chunk_type = "file chunk"
	Pattern = File_chunk_pattern
	Debug_pat = "filename"
	new_chunk = gensub(Pattern, "\\1", 1)
	if (Flags[Chunk_type]) {
		fatal(_"%s start of %s found while still collecting %s\n",
			Chunk_type, new_chunk, Current_chunk)
	}
	check_unfinished()
	
	Flags[Chunk_type] = TRUE
	Line_numbers[Chunk_type] = (FILENAME ":" FNR)
	Current_chunk = new_chunk
	Chunk_info[Current_chunk]["type"] = Chunk_type
	
	if (Debug ~ Debug_pat)
		printf("saw new %s %s\n", Debug_pat, Current_chunk) > "/dev/stderr"
	next
}
$0 ~ Code_chunk_pattern {
	Chunk_type = "code chunk"
	Pattern = Code_chunk_pattern
	Debug_pat = "code"
	new_chunk = gensub(Pattern, "\\1", 1)
	if (Flags[Chunk_type]) {
		fatal(_"%s start of %s found while still collecting %s\n",
			Chunk_type, new_chunk, Current_chunk)
	}
	check_unfinished()
	
	Flags[Chunk_type] = TRUE
	Line_numbers[Chunk_type] = (FILENAME ":" FNR)
	Current_chunk = new_chunk
	Chunk_info[Current_chunk]["type"] = Chunk_type
	
	if (Debug ~ Debug_pat)
		printf("saw new %s %s\n", Debug_pat, Current_chunk) > "/dev/stderr"
	next
}
/^@[[:space:]]*$/ {
	if (Flags["file chunk"])
		end_file_gathering()
	else if (Flags["code chunk"])
		end_code_gathering()
	else
		warning(_"unmatched terminating @-sign: ignored\n")

	Chunk_lines = ""
	Flags[Chunk_type] = FALSE
	Line_numbers[Chunk_type] = ""
	Chunk_type = ""

	next
}
Flags["file chunk"] || Flags["code chunk"] {
	if (Chunk_lines == "")
		Chunk_lines = $0
	else
		Chunk_lines = Chunk_lines "\n" $0

	next
}
function end_chunk_gathering()
{
	if (Pass == 1) {
		collect_chunk_info()
		Chunk_lines = ""
		Flags[Chunk_type] = FALSE
	} else
		print_out_chunk()
}

function end_file_gathering()
{
	end_chunk_gathering()
}
function collect_chunk_info(	i, n, x, called, junk)
{
	# Current_chunk, Chunk_type already set by initial code
	# Chunk number:
	if (! ("chunk number" in Chunk_info[Current_chunk])) {
		Chunk_info[Current_chunk]["chunk number"] = \
								++Chunk_numbers[Chunk_type]
	}

	# Definition instance
	Chunk_info[Current_chunk]["defn"]++

	# Get names of called chunks into called
	n = split(Chunk_lines, junk, Chunk_name_pattern, called)

	# Add ourselves to the callers
	for (i in called) {
		x = strip_out_name(called[i])
		Chunk_info[x]["callers"][Current_chunk] = TRUE
	}
}
function print_out_chunk(	x, y, n, i, parts, names,
							name, anchor, chunk_being_used)
{
	# Redefinition instance
	Chunk_info[Current_chunk]["redefn"]++

	print "@need 400"
	anchor = format_anchor(Current_chunk,
				Chunk_info[Current_chunk]["redefn"],
				Chunk_info[Current_chunk]["defn"])
	printf("%s\n", anchor)
	x = expand_tabs(Chunk_lines, Tabstop)
	# extract code chunks
	n = split(x, parts, Chunk_name_pattern, names)
	
	# escape special chars in parts of code that aren't chunk names
	for (i = 1; i in parts; i++)
		gsub(/[@{}]/, "@&", parts[i])
	y = parts[1]
	if (n > 1) {	# embedded chunk names
		for (i = 1; i in names; i++) {
			name = strip_out_name(names[i])
			chunk_being_used = \
				format_chunk_name(name, Chunk_info[name]["chunk number"],
					Chunk_info[name]["type"])
			printf("@crindex %s, use\n", chunk_being_used)
			y = y chunk_being_used
			y = y parts[i+1]
		}
	}
	chunk_being_defined = \
		format_chunk_name(Current_chunk,
							Chunk_info[Current_chunk]["chunk number"],
							Chunk_type)
	printf("@cdindex %s, definition\n", chunk_being_defined)
	printf("@noindent\n%s %s@equiv{}\n",
		chunk_being_defined,
		Chunk_info[Current_chunk]["redefn"] == 1 ? "" : "+")
	print Example_start
	printf("%s\n", y)
	print Example_end
	for (i in names)
		names[i] = strip_out_name(names[i])	# remove delimiters
	
	print "@iftex"
	print "@smallfonts @rm"
	print "@end iftex"
	# Print other definition sites for code chunks, or for file
	# chunk if Print_file_full_defs is true
	if (Chunk_type == "code chunk" || Print_file_full_defs) {
		print_other_defns(Current_chunk,
				Chunk_info[Current_chunk]["defn"],
				Chunk_info[Current_chunk]["redefn"])
	}
	# Print callers for code chunks
	if (Chunk_type == "code chunk") {
		if ("callers" in Chunk_info[Current_chunk]) {
			print ""
			asorti(Chunk_info[Current_chunk]["callers"], my_callers)
			if (length(my_callers) > 1) {
				print "@noindent"
				print "This chunk is called by the following chunks:\n"
				print_ref_table(my_callers)
			} else {
				n = Chunk_info[my_callers[1]]["defn"]
		
				print "@noindent"
				printf("This chunk is called by %s; see its first definition at %s.\n",
					format_chunk_name(my_callers[1],
								Chunk_info[my_callers[1]]["chunk number"],
								Chunk_info[my_callers[1]]["type"]),
					format_xref(my_callers[1], n > 1 ? 1 : 0))
			}
		} else
			warning(_"chunk %s has no callers\n", Current_chunk)
	}
	sort_and_remove_duplicates(names)
	switch (length(names)) {
	case 0:
		break
	case 1:
		print "\n@noindent"
		printf("The called chunk %s is first defined at\n%s.\n",
				format_chunk_name(names[1],
							Chunk_info[names[1]]["chunk number"],
							Chunk_info[names[1]]["type"]),
							format_xref(names[1],
								(Chunk_info[names[1]]["defn"] > 1) ? 1 : 0))
		break;
	default:
		print "\n@noindent"
		printf("The following table lists called chunk definition points.\n")
		print_ref_table(names)
		break;
	}
	print "@iftex"
	print "@textfonts @rm"
	print "@end iftex"
}
function print_other_defns(chunk, total_defns, current_defn,
							other_defns, i, j)	# locals
{
		if (total_defns == 1)
			return

		print ""
		print "@noindent"
		print "This chunk is also defined in"

		for (i = j = 1; i <= total_defns; i++) {
			if (i == current_defn)
				continue

			other_defns[j++] = i
		}

		for (i = 1; i < j; i++) {
			printf("%s", format_xref(chunk, other_defns[i]))
			if (i + 2 == j)
				print ", and"
			else if (i + 2 < j)
				print ","
		}
		print "."
}
function print_ref_table(chunklist,		i, x, n)
{
	print "@multitable @columnfractions .35 .65"
	print "@headitem Chunk name @tab First definition point"

	for (i = 1; i in chunklist; i++) {
		x = chunklist[i]
		n = (Chunk_info[x]["defn"] > 1) ? 1 : 0
		printf("@item %s @tab See %s.\n",
			format_chunk_name(x,
				Chunk_info[x]["chunk number"],
				Chunk_info[x]["type"]),
			format_xref(x, n))
	}

	print "@end multitable"
}
function sort_and_remove_duplicates(names,	i, dups)
{
	for (i in names)
		dups[names[i]] = 1

	asorti(dups)
	delete names
	for (i in dups)
		names[i] = dups[i]
}
function end_code_gathering()
{
	end_chunk_gathering()

	if (Debug ~ /code/)
		printf("finished formatting code %s\n",
					Code_chunk) > "/dev/stderr"
}
Pass == 2 && /^@print_file_defs[[:space:]]*$/ {
	delete Sorted_file_names
	j = 1
	for (i in Chunk_info) {
		if (Chunk_info[i]["type"] == "file chunk")
			Sorted_file_names[j++] = i
	}
	asort(Sorted_file_names)	# Sorted by value
	print "@table @asis"
	for (i = 1; i in Sorted_file_names; i++) {
		name = Sorted_file_names[i]
		x = format_chunk_name(name,
					Chunk_info[name]["chunk number"],
					Chunk_info[name]["type"])
		printf("@item %s\n", x)
		n = Chunk_info[name]["defn"]
		if (n == 1) {
			printf("This chunk is defined in\n")
			printf("%s.\n", format_xref(name, 0))
		} else {
			printf("Multiple definitions occur in\n")
			for (j = 1; j <= n; j++) {
				printf("%s", format_xref(name, j))
				if (j == n - 1)
					printf(",\nand\n")
				else if (j < n - 1)
					printf(",\n")
			}
			print ".\n"
		}
	}
	print "@end table"
	next
}
Pass == 2 && /^@print_code_defs[[:space:]]*$/ {
	delete Sorted_code_names
	j = 1
	for (i in Chunk_info) {
		if (Chunk_info[i]["type"] == "code chunk")
			Sorted_code_names[j++] = i
	}
	asort(Sorted_code_names)	# Sorted by value
	print "@table @asis"
	for (i = 1; i in Sorted_code_names; i++) {
		name = Sorted_code_names[i]
		x = format_chunk_name(name,
					Chunk_info[name]["chunk number"],
					Chunk_info[name]["type"])
		printf("@item %s\n", x)
		n = Chunk_info[name]["defn"]
		if (n == 1) {
			printf("This chunk is defined in\n")
			printf("%s.\n", format_xref(name, 0))
		} else {
			printf("Multiple definitions occur in\n")
			for (j = 1; j <= n; j++) {
				printf("%s", format_xref(name, j))
				if (j == n - 1)
					printf(",\nand\n")
				else if (j < n - 1)
					printf(",\n")
			}
			print ".\n"
		}
	}
	print "@end table"
	next
}
Pass == 2 && /^@print_code_refs[[:space:]]*$/ {
	delete Sorted_code_names
	j = 1
	for (i in Chunk_info) {
		if (Chunk_info[i]["type"] == "code chunk")
			Sorted_code_names[j++] = i
	}
	asort(Sorted_code_names)	# Sorted by value
	print "@table @asis"
	for (i = 1; i in Sorted_code_names; i++) {
		name = Sorted_code_names[i]
	
		n = Chunk_info[name]["defn"]
		if (n == 0)		# warning printed elsewhere
			continue
	
		fmt_name = format_chunk_name(name, Chunk_info[name]["chunk number"],
								Chunk_info[name]["type"])
		printf("@item %s\n", fmt_name)
	
		Current_chunk = name	# for use by next chunk
		if ("callers" in Chunk_info[Current_chunk]) {
			print ""
			asorti(Chunk_info[Current_chunk]["callers"], my_callers)
			if (length(my_callers) > 1) {
				print "@noindent"
				print "This chunk is called by the following chunks:\n"
				print_ref_table(my_callers)
			} else {
				n = Chunk_info[my_callers[1]]["defn"]
		
				print "@noindent"
				printf("This chunk is called by %s; see its first definition at %s.\n",
					format_chunk_name(my_callers[1],
								Chunk_info[my_callers[1]]["chunk number"],
								Chunk_info[my_callers[1]]["type"]),
					format_xref(my_callers[1], n > 1 ? 1 : 0))
			}
		} else
			warning(_"chunk %s has no callers\n", Current_chunk)
	}
	print "@end table"
	next
}
/^@print_initial_setup([[:space:]]+.*|[[:space:]]*)$/ {
	Print_initial_setup = TRUE
	if (NF > 1) {
		$1 = ""
		$0 = $0
		Initial_setup_name = $0
	}
	else
		Initial_setup_name = "Initial setup"

	next
}

/^@initial_setup[[:space:]]*$/, /^@end initial_setup[[:space:]]*$/ {
	if (Pass == 1 || ! Print_initial_setup)
		next

	Chunk_info[Initial_setup_name]["type"] = "code chunk"
	Chunk_info[Initial_setup_name]["defn"] = 1
	if (/^@initial_setup[[:space:]]*$/) {
		print "@need 400"
		printf("%s\n", format_anchor(Initial_setup_name, 0))
		printf("@noindent\n%s @equiv{}\n",
			format_chunk_name(Initial_setup_name, 0, "code chunk"))
		print Example_start
	} else if (/^@end initial_setup[[:space:]]*$/) {
		print Example_end
	} else {
		x = expand_tabs($0, Tabstop)
		gsub(/[@{}]/, "@&", x)
		print x
	}

	next
}
Pass == 2 { print }
BEGIN {
	if (Tabstop == 0)
		Tabstop = 4		# default tab stops
}
# expand_tabs --- expand tabs in the string

function expand_tabs(string, tabstop,	chars, out, i, j, k, n)
{
	if (tabstop < 2)
		fatal(_"expand_tabs: tabstop %d < 2\n", tabstop)

	n = split(string, chars, "")
	j = k = 0
	for (i = 1; i <= n;) {
		if (chars[i] == "\n") {
			out[j++] = chars[i++]
			k = 0
			continue
		}

		if (chars[i] != "\t") {
			out[j++] = chars[i++]
			k++
			continue
		}
		i++	# skip the tab

		do {
			out[j++] = " "
			k++
		} while (and(k, tabstop-1) != 0)
	}

	return join(out, 0, j, SUBSEP)
}
function sanitize_name(name)
{
	gsub(/[^[:alnum:]]/, "-", name)

	return name
}
function format_anchor_or_ref(type, name, defn,
								clean_name, result)	# locals
{
	clean_name = sanitize_name(name)
	if (defn > 0)
		result = sprintf("@%s{%s-%d}", type, clean_name, defn)
	else
		result = sprintf("@%s{%s}", type, clean_name)

	return result
}

function format_xref(name, defn)
{
	return format_anchor_or_ref("ref", name, defn)
}
function format_anchor(name, cur_defn, total_defns,		defn)
{
	if (total_defns == 1)
		defn = 0
	else if (cur_defn <= total_defns)
		defn = cur_defn
	return format_anchor_or_ref("anchor", name, defn)
}
function format_chunk_name(name, count, type,
							result, left, right, style) # locals
{
	if (type == "file chunk") {
		left = "@{"
		right = "@}"
		style = "file"
	} else if (type == "code chunk") {
		left = "<"
		right = ">"
		style = "i"
	} else
		fatal(_"format_chunk_name: Unknown chunk type `%s'\n", type)

	if (count > 0 && Numbered_chunks)
		result = sprintf("@r{%s@%s{%s} @oldnum{%d}%s}",
						left, style, name, count, right)
	else
		result = sprintf("@r{%s@%s{%s}%s}", left, style, name, right)

	return result
}
